home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Audio, Video & Photo / Songbird 0.7.0 / Songbird_0.7.0_windows-i686-msvc8.exe / components / sbMediaPageManager.js < prev    next >
Text File  |  2008-08-06  |  15KB  |  477 lines

  1. /**
  2. //
  3. // BEGIN SONGBIRD GPL
  4. // 
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 POTI, Inc.
  8. // http://songbirdnest.com
  9. // 
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. // 
  13. // Software distributed under the License is distributed 
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
  15. // express or implied. See the GPL for the specific language 
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this 
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc., 
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. // 
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26. const Cc = Components.classes;
  27. const Ci = Components.interfaces;
  28. const Cr = Components.results;
  29. const Ce = Components.Exception;
  30. const Cu = Components.utils;
  31.  
  32. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  33. Cu.import("resource://app/jsmodules/ArrayConverter.jsm");
  34. Cu.import("resource://app/jsmodules/RDFHelper.jsm");
  35. Cu.import("resource://app/jsmodules/sbProperties.jsm");
  36.  
  37. function MediaPageManager() {
  38. }
  39.  
  40. MediaPageManager.prototype = {
  41.   classDescription: "Songbird MediaPage Manager",
  42.   classID:          Components.ID("{e63463d0-357c-4035-af33-db670ee1b7f2}"),
  43.   contractID:       "@songbirdnest.com/Songbird/MediaPageManager;1",
  44.   QueryInterface:   XPCOMUtils.generateQI([Ci.sbIMediaPageManager]),
  45.   
  46.   _pageInfoArray: [],
  47.   
  48.   // PageInfo objects for the fallback mediapages.  Set by _registerDefaults.
  49.   _defaultPlaylistPage:  null,
  50.   _defaultFilteredPlaylistPage: null,
  51.   
  52.   
  53.   registerPage: function(aName, aURL, aMatchInterface) {
  54.   
  55.     // Make sure we don't already have a page with
  56.     // the given url
  57.     var pageInfo;
  58.     
  59.     for each (pageInfo in this._pageInfoArray) {
  60.       if (pageInfo.contentUrl == aURL) {
  61.         throw new Error("Page URL already registered: " + aURL);
  62.       }
  63.     }
  64.     
  65.     // Make a PageInfo object
  66.     pageInfo = {
  67.       get contentTitle() { 
  68.         return aName;
  69.       },
  70.       get contentUrl() {
  71.         return aURL;
  72.       },
  73.       get matchInterface() {
  74.         return aMatchInterface;
  75.       },
  76.       QueryInterface: function(iid) {
  77.         if (!iid.equals(Ci.sbIMediaPageInfo) &&
  78.             !iid.equals(Ci.nsISupports))
  79.           throw Cr.NS_ERROR_NO_INTERFACE;
  80.         return this;
  81.       }
  82.     };
  83.     
  84.     // Store the page into our page array
  85.     this._pageInfoArray.push(pageInfo);
  86.     
  87.     // And return it to the caller so he may use that to unregister
  88.     return pageInfo;
  89.   },
  90.   
  91.   unregisterPage: function(aPageInfo) {
  92.  
  93.     // If unregistering the default pages, must remove shortcut pointers
  94.     if (this._defaultFilteredPlaylistPage && 
  95.         this._defaultFilteredPlaylistPage.contentUrl == aPageInfo.contentUrl) {
  96.       this._defaultFilteredPlaylistPage = null;
  97.     } else if (this._defaultPlaylistPage && 
  98.       this._defaultPlaylistPage.contentUrl == aPageInfo.contentUrl) {
  99.       this._defaultPlaylistPage = null;
  100.     }
  101.   
  102.     // Search the array for the matching page
  103.     for (var i in this._pageInfoArray) {
  104.       if (aPageInfo.contentUrl == this._pageInfoArray[i].contentUrl) {
  105.         // found, remove it and stop
  106.         this._pageInfoArray.splice(i, 1);
  107.         return;
  108.       }
  109.     }
  110.     // Page not found, throw!
  111.     throw new Error("Page " + aPageInfo.contentTitle + " not found in unregisterPage");
  112.   },
  113.   
  114.   getAvailablePages: function(aList) {
  115.     this._ensureMediaPageRegistration();
  116.     // If no list is provided, return the entire set
  117.     if (!aList) {
  118.       return ArrayConverter.enumerator(this._pageInfoArray); 
  119.     }
  120.     // Otherwise, make a list of what matches the list
  121.     var tempArray = [];
  122.     for (var i in this._pageInfoArray) {
  123.       var pageInfo = this._pageInfoArray[i];
  124.       if (pageInfo.matchInterface.match(aList)) {
  125.         tempArray.push(pageInfo);
  126.       }
  127.     }
  128.     
  129.     // ... and return that.
  130.     return ArrayConverter.enumerator(tempArray); 
  131.   },
  132.   
  133.   getPage: function(aList) {
  134.     this._ensureMediaPageRegistration();
  135.     
  136.     // Read the saved state
  137.     var remote = Cc["@songbirdnest.com/Songbird/DataRemote;1"]
  138.                  .createInstance(Ci.sbIDataRemote);
  139.     remote.init("mediapages." + aList.guid, null);
  140.     var savedPageURL = remote.stringValue;
  141.     if (savedPageURL && savedPageURL != "") {
  142.       // Check that the saved url is still registered 
  143.       // and still supports this list
  144.       var pageInfo = this._checkPageForList(aList, savedPageURL);
  145.       if (pageInfo) return pageInfo;
  146.     }
  147.     
  148.     // Read the list's default
  149.     var defaultPageURL = aList.getProperty(SBProperties.defaultMediaPageURL);
  150.     if (defaultPageURL && defaultPageURL != "") {
  151.       // Check that the saved url is still registered 
  152.       // and still supports this list
  153.       var pageInfo = this._checkPageForList(aList, defaultPageURL);
  154.       if (pageInfo) return pageInfo;
  155.     }
  156.     
  157.     // No saved state and no default, this is either the first time this list
  158.     // is shown, or its previous saved/default page isn't valid anymore, so
  159.     // pick a new one
  160.     
  161.     // Hardcoded first run logic:
  162.     // Libraries get filter lists, playlists do not.
  163.     if (aList instanceof Ci.sbILibrary && this._defaultFilteredPlaylistPage) {
  164.       return this._defaultFilteredPlaylistPage;
  165.     } else if (this._defaultPlaylistPage)  {
  166.       return this._defaultPlaylistPage;
  167.     } else {
  168.       // No hardcoded defaults.  Look for anything
  169.       // that matches.
  170.       for (var i in this._pageInfoArray) {
  171.         var pageInfo = this._pageInfoArray[i];
  172.         if (pageInfo.matchInterface.match(aList)) {
  173.           return pageInfo;
  174.         }
  175.       }
  176.     }
  177.     
  178.     // Oh crap.
  179.     throw new Error("MediaPageManager unable to determine a page for " + aList.guid);
  180.  
  181.     // keep js happy ?
  182.     return null;
  183.   },
  184.   
  185.   setPage: function(aList, aPageInfo) {
  186.     // Save the state
  187.     var remote = Cc["@songbirdnest.com/Songbird/DataRemote;1"]
  188.                  .createInstance(Ci.sbIDataRemote);
  189.     remote.init("mediapages." + aList.guid, null);
  190.     remote.stringValue = aPageInfo.contentUrl;
  191.   },
  192.   
  193.   // internal. checks that a url is registered in the list of pages, and that 
  194.   // its matching test succeeds for a given list
  195.   _checkPageForList: function(aList, aUrl) {
  196.     for (var i in this._pageInfoArray) {
  197.       var pageInfo = this._pageInfoArray[i];
  198.       if (pageInfo.contentUrl != aUrl) continue;
  199.       if (!pageInfo.matchInterface.match(aList)) continue;
  200.       return pageInfo;
  201.     }
  202.     return null;
  203.   },
  204.  
  205.   _ensureMediaPageRegistration: function() {
  206.     if(this._registrationComplete) { return };
  207.     
  208.     this._registerDefaults();
  209.     MediaPageMetadataReader.loadMetadata(this);
  210.     
  211.     this._registrationComplete = true;
  212.   },
  213.   
  214.   _registerDefaults: function() {
  215.     var playlistString = "mediapages.playlistpage";
  216.     var filteredPlaylistString = "mediapages.filteredplaylistpage";
  217.     try {
  218.       var stringBundleService =
  219.           Components.classes["@mozilla.org/intl/stringbundle;1"]
  220.                     .getService(Components.interfaces.nsIStringBundleService);
  221.       var stringBundle = stringBundleService.createBundle(
  222.            "chrome://songbird/locale/songbird.properties" );
  223.       playlistString = stringBundle.GetStringFromName(playlistString);
  224.       filteredPlaylistString = stringBundle.GetStringFromName(
  225.                   filteredPlaylistString);
  226.       stringBundleService = null;
  227.       stringBundle = null;
  228.     } catch (e) {
  229.       Component.utils.reportError("MediaPageManager: Couldn't localize default media page name.\n")
  230.     }
  231.  
  232.     // the default page matches everything    
  233.     var matchAll = {match: function(mediaList) { return(true); }};
  234.  
  235.     // Register the playlist with filters
  236.     this._defaultFilteredPlaylistPage =
  237.         this.registerPage( filteredPlaylistString,
  238.         "chrome://songbird/content/mediapages/filtersPage.xul", 
  239.         matchAll);
  240.  
  241.     // And the playlist without filters
  242.     this._defaultPlaylistPage = 
  243.         this.registerPage( playlistString,
  244.         "chrome://songbird/content/mediapages/playlistPage.xul",
  245.         matchAll);
  246.   },
  247.   
  248. } // MediaPageManager.prototype
  249.  
  250.  
  251.  
  252. /**
  253.  * MediaPageMetadataReader
  254.  * Reads the Add-on Metadata RDF datasource for Media Page declarations.
  255.  */
  256. var MediaPageMetadataReader = {
  257.   loadMetadata: function(manager) {
  258.     this._manager = manager;
  259.     
  260.     var addons = RDFHelper.help(
  261.       "rdf:addon-metadata",
  262.       "urn:songbird:addon:root",
  263.       RDFHelper.DEFAULT_RDF_NAMESPACES
  264.     );
  265.     
  266.     for (var i = 0; i < addons.length; i++) {
  267.       // skip addons with no panes.
  268.       if (!addons[i].mediaPage) 
  269.         continue;
  270.       try {
  271.         var pages = addons[i].mediaPage;
  272.         for (var j = 0; j < pages.length; j++) {
  273.           this._registerMediaPage(addons[i], pages[j]) 
  274.         }
  275.       } catch (e) {
  276.         this._reportErrors("", [  "An error occurred while processing " +
  277.                   "extension " + addons[i].Value + ".  Exception: " + e  ]);
  278.       }
  279.     }
  280.   },
  281.   
  282.   /**
  283.    * Extract pane metadata and register it with the manager.
  284.    */
  285.   _registerMediaPage: function _registerMediaPage(addon, page) {
  286.     // create and validate our page info
  287.     var errorList = [];
  288.     var warningList = [];
  289.     
  290.     var info = {};
  291.     for (property in page) {
  292.       if (page[property])
  293.        info[property] = page[property][0];
  294.     }
  295.     
  296.     this._validateProperties(info, errorList, warningList);
  297.     
  298.     // create a match function
  299.     var matchFunction;
  300.     if (page.match) {
  301.       var matchList = this._createMatchList(page, warningList);
  302.       matchFunction = this._createMatchFunction(matchList);
  303.     }
  304.     else {
  305.       matchFunction = this._createMatchAllFunction();
  306.     }
  307.     
  308.     // If errors were encountered, then do not submit
  309.     if (warningList.length > 0){
  310.       this._reportErrors(
  311.           "Warning: " + addon.Value + " install.rdf loading media page: " , warningList);
  312.     } 
  313.     if (errorList.length > 0) {
  314.       this._reportErrors(
  315.           "ERROR: " + addon.Value + " install.rdf IGNORED media page: ", errorList);
  316.       return;
  317.     }
  318.     
  319.     // Submit description
  320.     this._manager.registerPage( info.contentTitle,
  321.                                 info.contentUrl,
  322.                                 {match: matchFunction}
  323.                                );
  324.     
  325.     //dump("MediaPageMetadataReader: registered pane " + info.contentTitle
  326.     //     + " at " + info.contentUrl + " from addon " + addon.Value + " \n");
  327.   },
  328.   
  329.   _validateProperties: function(info, errorList, warningList) {
  330.     var requiredProperties = ["contentTitle", "contentUrl"];
  331.     var optionalProperties = ["match"];
  332.     
  333.     // check for required properties
  334.     for (var p in requiredProperties) { 
  335.       if (!info[requiredProperties[p]]) {
  336.         errorList.push("Missing required property " + requiredProperties[p] + ".\n")
  337.       }
  338.     }
  339.     
  340.     // check for unused RDF nodes and warn about them.
  341.     var template = {};
  342.     for(var i in requiredProperties) {
  343.       template[requiredProperties[i]] = "required";
  344.     }
  345.     for(var i in optionalProperties) {
  346.       template[optionalProperties[i]] = "optional";
  347.     }
  348.     
  349.     for (var p in info) {
  350.       if (!template[p]) {
  351.         warningList.push("Unrecognized property " + p + ".\n")
  352.       }
  353.     }
  354.   },
  355.   
  356.   _createMatchList: function(page, warningList) {
  357.     // create a set of property-comparison objects
  358.     // one for each <match>. a mediapage will work for medialists which match
  359.     // all the properties in a hash.
  360.     var matchList = [];
  361.     
  362.     for (var i = 0; i < page.match.length; i++) {
  363.       var fields = page.match[i].split(/\s+/);
  364.       var properties = {}
  365.       for(var f in fields) {
  366.         var key; var value;
  367.         [key, value] = fields[f].split(":");
  368.         if(!value) { value = key; key = "type" };
  369.         
  370.         // TODO: quoted string values ala name:"My Funky List"
  371.         // TODO: check if the key is actually a legal key-type and warn if not
  372.         
  373.         if(properties[key]) {
  374.            warningList.push("Attempting to match two values for "
  375.                             +key+": "+properties[key]+" and "+value+".");
  376.         }
  377.         
  378.         properties[key] = value;
  379.       }
  380.       
  381.       matchList.push(properties); 
  382.     }
  383.     
  384.     return matchList;
  385.   },
  386.   
  387.   _createMatchFunction: function(matchList) {
  388.     // create a match function that uses the match options
  389.     var matchFunction = function(mediaList) {
  390.       // just in case someone's passing in bad values
  391.       if(!mediaList) {
  392.         return false;
  393.       }
  394.       
  395.       // check each <match/>'s values
  396.       // if any one set works, this is a good media page for the list
  397.       for (var m in matchList) {
  398.         match = matchList[m];
  399.         
  400.         // first, see if we just want to opt out of this match
  401.         // our definition of an opt-out-able list is:
  402.         // one that is so unspecific as to only target by "type"
  403.         // or to target everything. (see below)
  404.         // (this is a bit natty)
  405.         var numProperties = 0;
  406.         for (var i in match) {
  407.           numProperties++
  408.         }
  409.         // if we *only* target the "type" of the list
  410.         // and the list wants to opt out
  411.         if (numProperties == 1 && match["type"]) {
  412.           if (mediaList.getProperty(SBProperties.onlyCustomMediaPages) == "1") {
  413.             return false;
  414.           }
  415.         }
  416.         
  417.         var thisListMatches = true;
  418.         for (var i in match) {
  419.           // Use getProperty notation if the desired field is not
  420.           // specified as a true JS property on the object.
  421.           // TODO: This should be improved.
  422.           var comparisonValue;
  423.           if (mediaList[i] == undefined) {
  424.             comparisonValue = mediaList.getProperty(SBProperties[i]);
  425.             // this is somewhat icky
  426.           }
  427.           else {
  428.             comparisonValue = mediaList[i];
  429.           }
  430.  
  431.           if (match[i] != comparisonValue) {
  432.             thisListMatches = false;
  433.             break;
  434.           }
  435.         }
  436.         if (thisListMatches) {
  437.           return true;
  438.         }
  439.       }
  440.       
  441.       // arriving here means none of the <match>
  442.       // elements fit the medialist passed in
  443.       return false;
  444.     }
  445.     
  446.     return matchFunction;
  447.   },
  448.   
  449.   _createMatchAllFunction: function() {
  450.     var matchFunction = function(mediaList) {
  451.       // opt-out lists will also exclude completely generic pages
  452.       // this detail must be clearly communicated to the MP dev'rs!
  453.       return(mediaList && mediaList.getProperty(SBProperties.onlyCustomMediaPages) != "1");
  454.     };
  455.     return matchFunction;
  456.   },
  457.   
  458.   /**
  459.    * \brief Dump a list of errors to the console and jsconsole
  460.    *
  461.    * \param contextMessage Additional prefix to use before every line
  462.    * \param errorList Array of error messages
  463.    */
  464.   _reportErrors: function _reportErrors(contextMessage, errorList) {
  465.     var consoleService = Components.classes["@mozilla.org/consoleservice;1"].
  466.          getService(Components.interfaces.nsIConsoleService);
  467.     for (var i = 0; i  < errorList.length; i++) {
  468.       Components.utils.reportError("MediaPage Addon Metadata: " + contextMessage + errorList[i]);
  469.     }
  470.   }
  471. }
  472.  
  473. function NSGetModule(compMgr, fileSpec) {
  474.   return XPCOMUtils.generateModule([MediaPageManager]);
  475. }
  476.  
  477.